/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CoapExchange.h"

#include <sys/stat.h>
#include <unistd.h>

#include <string>

#include "coap3/coap_internal.h"
#include "third_party_bounds_checking_function/include/securec.h"

static const int FIRST = 0;
static const int SECOND = 1;
static const int THIRD = 2;
static const int COUNT_ONE = 1;
static const int COUNT_TWO = 2;
static const int COUNT_THREE = 3;

static const int DEFAULT_BUFF_SIZE = 1024;
static const int OPTIONS_BUFF_SIZE = 10240;  // 10KB
static const int DATA_BUFF_SIZE = 524288 ;   // 5000KB
static std::unordered_map<std::string, CoapExchange::ResponseTool> g_responseMap;

CoapExchange::ResponseTool CoapExchange::SetResponseTool(coap_resource_t *resource, coap_session_t *session,
                                                         const coap_pdu_t *request, const coap_string_t *query,
                                                         coap_pdu_t *response)
{
    CoapExchange::ResponseTool responseTool;
    responseTool.resource = resource;
    responseTool.session = session;
    responseTool.request = request;
    responseTool.query = query;
    responseTool.response = response;
    responseTool.sem;
    sem_init(&(responseTool.sem), 0, 0);
    return responseTool;
}

void CoapExchange::AddResponse(std::string responseId, CoapExchange::ResponseTool responseTool)
{
    g_responseMap.insert(std::pair<std::string, CoapExchange::ResponseTool>(responseId, responseTool));
}

void CoapExchange::Destroy(std::string responseId)
{
    auto iter = g_responseMap.find(responseId);
    if (iter != g_responseMap.end()) {
        g_responseMap.erase(iter);
    }
}

void CoapExchange::Wait(std::string responseId)
{
    sem_wait(&g_responseMap[responseId].sem);
}

napi_value CoapExchange::Response(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3];
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_THREE) {
        napi_throw_type_error(env, nullptr, "Expected at least 3 argument for Response.");
        return nullptr;
    }

    char responseId[DEFAULT_BUFF_SIZE] = {0};
    size_t charLen = sizeof(responseId) - 1;
    status = napi_get_value_string_utf8(env, args[FIRST], responseId, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument 1 to UTF-8 string.");
        return nullptr;
    }

    int code;
    status = napi_get_value_int32(env, args[SECOND], &code);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument 2 to int32.");
        return nullptr;
    }

    char data[DATA_BUFF_SIZE] = {0};
    charLen = sizeof(data) - 1;
    status = napi_get_value_string_utf8(env, args[THIRD], data, charLen, &charLen);
    if (status != napi_ok || charLen >= DATA_BUFF_SIZE) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument 3 to UTF-8 string (must < 0.5M).");
        return nullptr;
    }

    if (g_responseMap.find(responseId) == g_responseMap.end()) {
        LOGI("response failed: not found id");
        return nullptr;
    }
    CoapExchange::ResponseTool tmp = g_responseMap[responseId];
    coap_pdu_set_code(tmp.response, (coap_pdu_code_t)COAP_RESPONSE_CODE(code));
    coap_add_data_large_response(tmp.resource, tmp.session, tmp.request, tmp.response, tmp.query,
                                 COAP_MEDIATYPE_TEXT_PLAIN, 0x2ffff, 0, strlen(data), (const uint8_t *)data, NULL,
                                 NULL);

    sem_post(&g_responseMap[responseId].sem);
    return nullptr;
}

static void RtagToHex(coap_opt_t *option, char *optionData, size_t size)
{
    // 获取RTAG选项的长度
    const int rexSize = 2;
    size_t len = coap_opt_length(option);
    if (len * rexSize >= size) {
        return;
    }

    // 打印RTAG的十六进制表示
    for (size_t i = 0; i < len; ++i) {
        (void)sprintf_s(&optionData[i * rexSize], size, "%02X", ((unsigned char *)coap_opt_value(option))[i]);
    }
}

napi_value CoapExchange::GetRequestOptions(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];
    napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected at least 1 argument for GetRequestOptions.");
        return nullptr;
    }

    char responseId[DEFAULT_BUFF_SIZE] = {0};
    size_t charLen = sizeof(responseId) - 1;
    napi_get_value_string_utf8(env, args[FIRST], responseId, charLen, &charLen);
    if (g_responseMap.find(responseId) == g_responseMap.end()) {
        LOGI("response failed: not found id");
        return nullptr;
    }

    CoapExchange::ResponseTool tmp = g_responseMap[responseId];
    coap_opt_t *option;
    coap_opt_iterator_t opt_iter;
    std::string retStr("[");
    coap_option_iterator_init(tmp.request, &opt_iter, COAP_OPT_ALL);
    int backupNum = 0;
    while ((option = coap_option_next(&opt_iter))) {
        char item[OPTIONS_BUFF_SIZE] = {0};
        char optionData[OPTIONS_BUFF_SIZE] = {0};
        int len = coap_opt_length(option);
        const char *pOption = (const char *)coap_opt_value(option);

        // COAP_OPTION_RTAG值为二进制，需要转换
        if (opt_iter.number == COAP_OPTION_RTAG) {
            RtagToHex(option, optionData, OPTIONS_BUFF_SIZE);
        } else {
            if (memcpy_s(optionData, sizeof(optionData), pOption, len) != 0) {
                LOGI("memcpy_s failed");
                continue;
            }
        }
        int n = sprintf_s(item, sizeof(item), "{\"number\": %d,\"length\": %d,\"value\": \"%s\"},",
            opt_iter.number, len, optionData);
        if (n > 0) {
            retStr += item;
            backupNum = 1;
        } else {
            LOGI("%d option too long: %d", opt_iter.number, len);
        }
    }
    retStr = retStr.substr(0, retStr.length() - backupNum) + ']';

    napi_value ret;
    napi_create_string_utf8(env, retStr.c_str(), NAPI_AUTO_LENGTH, &ret);
    return ret;
}

napi_value CoapExchange::GetRequestText(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected at least 1 argument for GetRequestText.");
        return nullptr;
    }

    char responseId[DEFAULT_BUFF_SIZE] = {0};
    size_t charLen = sizeof(responseId) - 1;
    status = napi_get_value_string_utf8(env, args[FIRST], responseId, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    if (g_responseMap.find(responseId) == g_responseMap.end()) {
        LOGI("response failed: not found id");
        return nullptr;
    }

    const uint8_t *data;
    size_t size;
    CoapExchange::ResponseTool tmp = g_responseMap[responseId];
    coap_get_data(tmp.request, &size, &data);

    if (!data) {
        return nullptr;
    }
    napi_value ret;
    napi_create_string_utf8(env, (char *)data, size, &ret);

    return ret;
}

napi_value CoapExchange::GetSourceAddress(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1];
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc < COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected at least 1 argument for GetSourceAddress.");
        return nullptr;
    }

    char responseId[DEFAULT_BUFF_SIZE] = {0};
    size_t charLen = sizeof(responseId) - 1;
    status = napi_get_value_string_utf8(env, args[FIRST], responseId, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    if (g_responseMap.find(responseId) == g_responseMap.end()) {
        LOGI("response failed: not found id");
        return nullptr;
    }

    char addr[DEFAULT_BUFF_SIZE] = {0};
    CoapExchange::ResponseTool tmp = g_responseMap[responseId];
    coap_print_ip_addr(&tmp.session->addr_info.remote, addr, sizeof(addr));

    napi_value ret;
    napi_create_string_utf8(env, (char *)addr, NAPI_AUTO_LENGTH, &ret);

    return ret;
}

napi_value CoapExchange::JsConstructor(napi_env env, napi_callback_info info)
{
    napi_value targetObj = nullptr;
    void *data = nullptr;
    size_t argc = 0;
    napi_value args[1] = {nullptr};

    napi_status status = napi_get_cb_info(env, info, &argc, args, &targetObj, &data);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to retrieve callback info");
        return nullptr;
    }

    CoapExchange *classBind = new CoapExchange();
    napi_wrap(
        env, nullptr, classBind,
        [](napi_env env, void *data, void *hint) {
            CoapExchange *bind = (CoapExchange *)data;
            delete bind;
            bind = nullptr;
        },
        nullptr, nullptr);
    return targetObj;
}